#! /usr/bin/env python2
#-*- encoding:utf-8 -*-
#文件名:gtkdocpo
"""
gtkdocpo is a tool to create a pot file from C source files that used gtkdoc to generate API documents, and to merge the l10n po file to the C source files.
"""
__version__  = "0.2"
__date__     = "2011-04-13 20:25:37"
__author__   = "yetist <yetist@gmail.com> "
__license__  = "Licensed under the GPL v2, see the file LICENSE in this tarball."
__copyright__= "Copyright (C) 2006-2011 by yetist <yetist@gmail.com>."
#=================================================================================#
import os, sys
import re
import time
import shutil
from optparse import OptionParser, OptionGroup, HelpFormatter

# Utility functions
def escapePoString(text):
    return text.replace('\\','\\\\').replace('"', "\\\"").replace("\n","\\n").replace("\t","\\t")

def unEscapePoString(text):
    return text.replace("\\t", "\t").replace('\\"', '"').replace('\\\\','\\')

opts = {
    "command":0,
    "multi_files":True,
    "pkg_name":"PACKAGE",
    "include_code":False,
    "po_file_dir":os.getcwd()+'/gtkdocpo',
    "look":True,
    "fuzzy":False,
    "pkg_ver":"VERSION"
    }


class Merge:
    def start (self, options, directory):
        self.opts = options
        self.src_dir = directory
        self.merge_data = {}

        if os.path.isdir (self.opts.po_file_dir):
            file = os.path.join(self.opts.po_file_dir, self.opts.pkg_name + "-" + self.opts.pkg_ver + ".po")
            if os.path.exists(file):
                    self._do_po_file(file)
#            else:
#                for item in os.listdir(self.opts.po_file_dir):
#                    if len(item) >3 and item.endswith(".po"):
#                        file = os.path.join(self.opts.po_file_dir, item)
#                        self._do_po_file(file)

        #self.show_merge()
        self.do_merge()

    def get_message(self, data, line, atype="msgstr"):
            # if not translate
            if atype=="msgid":
                break_str="msgstr"
            else:
                break_str="\"Project-Id-Version:"
            message=""
            n = line
            if data[line].find(atype) >=0 and len(data[line].split('"')) > 2:
                if data[line].split('"')[1] != '':
                    start = data[line].find('"') + 1
                    end = data[line].rfind('"')
                    msg = data[line][start:end]
                    if msg.endswith("\\n"):
                        msg = msg[:-2]
                    message += msg + '\n'
                    #message += data[line].split('"')[1]+"\n"
                else:
                    # if the next line is not empty, and
                    while n+1< len(data) and len(data[n+1].strip()) > 0 and data[n+1].split('"')[1] != "":
                        #message += ' * ' + data[n+1].split('"')[1]+'\n'
                        start = data[n+1].find('"') + 1
                        end = data[n+1].rfind('"')
                        msg = data[n+1][start:end]
                        if msg.endswith("\\n"):
                            msg = msg[:-2]
                        message += ' * ' + msg + '\n'
                        n = n+1
                if message.startswith(" * "):
                    message = message[3:]
            return (message, line, n-line)

    def _do_po_file (self, file_name):
        #print ">>>", file_name
        data = open (file_name).readlines()
        first_msgstr = True
        for i in range(5, len(data)):
            # if not translate
            if data[i].startswith("msgid"):
                (msgid, n1, cnt1) = self.get_message(data, i, "msgid")
                if data[n1+cnt1].startswith("msgstr"):
                    (msgstr, n2, cnt2) = self.get_message(data, n1+cnt1, "msgstr")
                else:
                    (msgstr, n2, cnt2) = self.get_message(data, n1+cnt1+1, "msgstr")
                #如果已经翻译则继续
                # if the msgstr has translated, then parser the source filename and line number.
                fuzzy_flag = False
                if len(data[i-1].split()) == 2 and data[i-1].split()[1] == "fuzzy":
                    fuzzy_flag = True
                    if self.opts.fuzzy:
                        fuzzy_flag = False
                if len(msgstr) > 0 and (not fuzzy_flag):
                    #msgstr = unEscapePoString(msgstr) + '\n'
                    msgstr = unEscapePoString(msgstr)

                    n = i
                    while n-1 > 0 and  len(data[n-1].strip()) > 0:
                        if data[n-1].startswith("#:"):
                            for item in data[n-1].split()[1:]:
                                src_file, start_line, count_line = item.split(":")
                                if not self.merge_data.has_key(src_file):
                                    self.merge_data[src_file]={}
                                self.merge_data[src_file][start_line]=(count_line, msgstr)
                        n = n -1

    def show_merge (self):
        for k in self.merge_data.keys():
            for sk in self.merge_data[k].keys():
                print "[", k,sk, self.merge_data[k][sk][0], "]", self.merge_data[k][sk][1]

    def do_merge (self):
        for f in self.merge_data.keys():
            src_file = os.path.join(self.src_dir, f)
            if not os.path.isfile(src_file):
                continue
            data = open(src_file).readlines()
            for l in self.merge_data[f].keys():
                #print "%s\t%s@%s --> %s" % (f, l, self.merge_data[f][l][0], self.merge_data[f][l][1])
                for i in range(int(self.merge_data[f][l][0])):
                    if i == 0:
                        line = data[int(l)-1+i]
                        data[int(l)-1+i] = line[:line.find("*")+1]+" "+ self.merge_data[f][l][1]
                    else:
                        data[int(l)-1+i] = None
            src_fd = open(src_file, "w")
            for i in data:
                if i:
                    src_fd.writelines(i)
            src_fd.close()

class Create:
    def write_header(self, fd):
        header="""# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: %s %s\\n"
"Report-Msgid-Bugs-To: \\n"
"POT-Creation-Date: %s\\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
"Language-Team: LANGUAGE <LL@li.org>\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n"

""" % (self.opts.pkg_name, self.opts.pkg_ver, time.strftime("%Y-%m-%d %H:%M")+"%+2.4d" % (time.timezone/36))
        fd.write(header)
    def start (self, options, directory):
        self.opts = options
        if os.path.isfile(directory) and (directory.endswith(".c")or directory.endswith(".h")):
            self._do_file(directory)
        elif os.path.isdir(directory):
            for item in os.listdir(directory):
                if len(item) >2:
                    if item.endswith(".c") or item.endswith(".h"):
                        file = os.path.join(directory, item)
                        self._do_file(file)

    def _do_file (self, file_name):
        if not os.path.isdir(self.opts.po_file_dir):
            os.mkdir(self.opts.po_file_dir)

        has_gtkdoc = False
        base_name = os.path.splitext(os.path.basename(file_name))[0]

        pot_file = os.path.join (self.opts.po_file_dir, base_name + ".pot~")
        pot_fd = open(pot_file, "w")

        data=open(file_name).readlines()
        for i in range(len(data)):
            cont=[] #content befort escape
            content=[] #content after escape.
            if data[i].strip() == "/**":
                if not has_gtkdoc:
                    has_gtkdoc = True
                    self.write_header(pot_fd)
                start_line = i
                while data[i+1].find("*/") < 0:
                    cont.append(data[i+1])
                    i = i+1
                end_line = i+2 #data[i+1] is "*/"

                function=data[start_line+1][data[start_line+1].find("*")+1:].strip() #start_line is "/**"
                for line in cont:
                    line = escapePoString(line)
                    if line.endswith("\\n"):
                        line = line[:-2]
                    content.append(line)  #content has string list that after escape.
                last_n=0
                for n in range(1, len(content)): #content[0] is function name
                    if n < last_n: #last process line number.
                        continue
                    # if this line is begin of @args
                    if re.search("^\*\ ?@\D+:",content[n].strip()):
                        begin=start_line+2+n
                        args=content[n][content[n].find("*")+1:].strip()
                        # if the next line is not begin of @args, and is not empty line, will append the next line to this line.(it is continued).
                        while n+1< len(content) and (not re.search("^\*\ ?@\D+:",content[n+1].strip())) and (not len(content[n+1].strip()) < 2): 
                            args = args + " " + content[n+1][content[n+1].find("*")+1:].strip()
                            n=n+1
                        last_n = n+1
                        end = start_line+2+n

                        pot_fd.writelines ("#. used by: %s\n" % function)
                        pot_fd.writelines ("#: %s:%d:%d\n" % (os.path.basename(file_name), begin, end-begin+1))
                        pot_fd.writelines ("msgid \"%s\"\n" % args)
                        pot_fd.writelines ("msgstr \"\"\n")
                        pot_fd.writelines ("\n")
                        continue

                    # if this line is empty, go on.
                    elif len(content[n].strip()) < 2:
                        continue
                    else:
                        # FIXME:
                        # if fount "<programlisting>","</programlisting>" and "|[", "]|"。
                        # if this line is begin of the code.
                        if content[n].find("<programlisting")>=0:
                            begin=start_line+2+n #the number 2 is to skip "/**" and "function name" line, add "n" for this line number.
                            codeline = n
                            while codeline +1 < len(content) and content[codeline +1].find("</programlisting>")<0:
                                codeline = codeline + 1
                            if self.opts.include_code:
                                pot_fd.writelines ( "#. ###[It's code, but some need to translate]:used by: %s\n" % function)
                                pot_fd.writelines ( "#: %s:%d:%d\n" % (os.path.basename(file_name), start_line+3+n, codeline-n))
                                pot_fd.writelines ( "#, no-wrap\n") #: %s:%d:%d\n" % (os.path.basename(file_name), start_line+3+n, 1))
                                pot_fd.writelines ( "msgid \"\"\n") #% content[n+1][content[n+1].find("*")+1:].rstrip())
                            while n+1 <= codeline:
                                if self.opts.include_code:
                                    pot_fd.writelines ("\"%s\\n\"\n" % content[n+1][content[n+1].find("*")+1:].rstrip())
                                n = n+1
                            if self.opts.include_code:
                                pot_fd.writelines ("msgstr \"\"\n")
                            last_n = codeline + 2 #skip "codeline+1 line is: </programlisting>, skip this line.
                            continue

                        if content[n].find("|[")>=0:
                            begin=start_line+2+n #the number 2 is to skip "/**" and "function name" line, add "n" for this line number.
                            codeline = n
                            while codeline < len(content) and content[codeline].find("]|")<0:
                                codeline = codeline + 1
                            if self.opts.include_code:
                                pot_fd.writelines ( "#. ###[It's code, but some need to translate]:used by: %s\n" % function)
                                pot_fd.writelines ( "#: %s:%d:%d\n" % (os.path.basename(file_name), start_line+3+n, codeline-n))
                                pot_fd.writelines ( "#, no-wrap\n") #: %s:%d:%d\n" % (os.path.basename(file_name), start_line+3+n, 1))
                                pot_fd.writelines ( "msgid \"\"\n") #% content[n+1][content[n+1].find("*")+1:].rstrip())
                            while n+1 <= codeline:
                                if self.opts.include_code:
                                    pot_fd.writelines ("\"%s\\n\"\n" % content[n+1][content[n+1].find("*")+1:].rstrip())
                                n = n+1
                            if self.opts.include_code:
                                pot_fd.writelines ("msgstr \"\"\n")
                            last_n = codeline + 2 #skip "codeline+1 line is: </programlisting>, skip this line.
                            continue

                        begin=start_line+2+n

                        description = content[n][content[n].find("*")+1:].strip()
                        while n+1 < len(content) and content[n+1].find("<programlisting")<0 and content[n+1].find("|[")<0 and (not len(content[n+1].strip()) < 2) :
                            description = description + " " + content[n+1][content[n+1].find("*")+1:].strip()
                            n=n+1
                        last_n = n+1
                        end = start_line+2+n
                        pot_fd.writelines ("#. used by: %s\n" % function)
                        pot_fd.writelines ("#: %s:%d:%d\n" % (os.path.basename(file_name), begin, end-begin+1))
                        pot_fd.writelines ("msgid \"%s\"\n" % description)
                        pot_fd.writelines ("msgstr \"\"\n")
                        pot_fd.writelines ("\n")
        pot_fd.close()
        if not has_gtkdoc:
            os.remove(pot_file)
        else:
            newpot_file = os.path.join (self.opts.po_file_dir, base_name + ".pot")
            if os.path.exists(newpot_file):
                self._po_uniq(pot_file)
                cat_file = os.path.join(self.opts.po_file_dir, base_name + "-tmp.pot")
                ret = os.system("msgcat -F %s %s -o %s >/dev/null 2>&1 " % (pot_file, newpot_file, cat_file)) 
                if ret == 0:
                    self._po_uniq(cat_file)
                    os.system("msgmerge -N -F %s %s -o %s >/dev/null 2>&1" % (pot_file, cat_file, newpot_file))
                    os.remove(pot_file)
                    os.remove(cat_file)
                    self._po_uniq(newpot_file)
            else:
               os.rename(pot_file, newpot_file)
               self._po_uniq(newpot_file)

    def _po_uniq(self, file_name):
        tempfile = os.tmpnam()
        ret = os.system("msguniq -F %s -o %s" % (file_name, tempfile))
        if ret == 0:
            shutil.move(tempfile, file_name)
            return True
        else:
            return False

def get_opts():
    MSG_USAGE = "%prog [OPTIONS] COMMAND SOURCE_DIR"
    parser = OptionParser(MSG_USAGE)
    parser.description="Create pot file from C source files that used gtkdoc, and merge po file to C source files."
    parser.defaults=opts

    parser.version="\n".join(["%prog " + __version__, __author__, __license__, __copyright__])
    parser.add_option("-v", "--version",
            dest = "version", action = "store_true",
            help="show version info and exit")
    parser.add_option("-e", "--example",
            dest = "example", action = "store_true",
            help="show example and exit")

    group = OptionGroup(parser, "COMMANDS")
    group.add_option("-c", "--create",
            dest = "command", action = "store_const", const = 0,
            help="Create pot file from C source file")
    group.add_option("-m", "--merge", 
            dest = "command", action = "store_const", const = 1,
            help = "Merge po file to C source file")
    parser.add_option_group(group)

    group = OptionGroup(parser, "Command Options")
    group.add_option("-1", "--single-file",
            dest = "multi_files", action = "store_false",
            help = "create a single pot file")
    group.add_option("-2", "--multi-files",
            dest = "multi_files", action = "store_true",
            help = "create multi pot files.")
    group.add_option("-n", "--package-name",
            dest = "pkg_name", action = "store", type = "string",
            help = "the package name used for pot file. default:PACKAGE.")
    group.add_option("-V", "--package-version",
            dest = "pkg_ver", action = "store", type = "string",
            help = "the package version used for pot file. default:VERSION")
    group.add_option("-f", "--fuzzy",
            dest = "fuzzy", action = "store_true",
            help = "if set, will use the fuzzy item in po file.")
    group.add_option("-i", "--include-code",
            dest = "include_code", action = "store_true",
            help = "if set, some code will show in msgid of pot file according gtkdoc format.")
    group.add_option("-p", "--po-file-dir",
            dest = "po_file_dir", action = "store", type="string",
            help = "directory that placed the po(t) file(s). default is \"gtkdocpo\" in current directory.")
    group.add_option("-l", "--look",
            dest = "look", action = "store_true",
            help = "if set, you can look that msgid and msgstr befort merge.")
    parser.add_option_group(group)

    return parser

Commands=[Create(), Merge()]

def main():
    parser = get_opts()
    opts, args = parser.parse_args()
    
    if opts.version:
        parser.print_version()
        sys.exit(0)

    if opts.example:
        parser.print_usage()
        print ("Example:\n")
        print ("1) create pot file(s) from \"/tmp/gtk+-2.10.6/gtk\", you can use:\n")
        print ("\t%s /tmp/gtk+-2.10.6/gtk\n"
                "\t\t-c is default command\n" % parser.get_prog_name())
        print ("\t%s -c /tmp/gtk+-2.10.6/gtk\n"
                "\t\tdefault pot dir is: ./gtkdocpo\n" % parser.get_prog_name())
        print ("\t%s -c -p pot /tmp/gtk+-2.10.6/gtk\n"
                "\t\tset pot dir\n" % parser.get_prog_name())
        print ("\t%s -c -n gtk+-2.10.6 /tmp/gtk+-2.10.6/gtk\n"
                "\t\tset PACKAGE in pot file\n" % parser.get_prog_name())
        print ("\t%s -c -1 /tmp/gtk+-2.10.6/gtk\n"
                "\t\tif create only one pot file, use -1\n" % parser.get_prog_name())

        print ("2) merge po file(s) to source file(s) in \"/tmp/gtk+-2.10.6/gtk\", you should use:\n")
        print ("\t%s -m /tmp/gtk+-2.10.6/gtk\n"
                "\t\tuse default pot dir: ./gtkdocpo\n" % parser.get_prog_name())
        print ("\t%s -m -p pot_dir /tmp/gtk+-2.10.6/gtk\n"
                "\t\tset pot dir\n" % parser.get_prog_name())
        print ("\t%s -m -f -p pot_dir /tmp/gtk+-2.10.6/gtk\n"
                "\t\tset pot dir, and use fuzzy item.\n" % parser.get_prog_name())
        sys.exit(0)

    if len(args) < 1:
        parser.print_help()
        sys.exit(1)
    elif len(args) == 1:
        argv = os.path.abspath(args[0])
        if (not os.path.isdir(argv)) and (not argv.endswith(".c")):
            parser.print_help()
            sys.exit(1)

    for i in args:
        args = os.path.abspath(i)
        Commands[opts.command].start(opts, args)

    # at last, check if make the single pot file.
    if (opts.command == 0 ) and ( not opts.multi_files ):
        os.system("msgcat -F %s/*.pot -o %s" % (opts.po_file_dir, os.path.join(opts.po_file_dir, opts.pkg_name+"-"+opts.pkg_ver+".pot")))
        for i in os.listdir(opts.po_file_dir):
            if i == opts.pkg_name+"-"+opts.pkg_ver+".pot":
                continue
            else:
                os.system("rm %s/%s" % (opts.po_file_dir, i))

if __name__=="__main__":
    main()

